﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FerrScript
{
    /// <summary>
    /// Used with the ScriptValue type, for a quick determination of value type
    /// </summary>
    public enum ScriptValueType
    {
        Number,
        String,
        Delegate,
        Object,
        Function, 
        FunctionCall,
        Variable, 
        Type
    }

    /// <summary>
    /// This is the container for the values that the scripts work with, it can
    /// be changed to a struct to cut down on garbage collections, with a slight 
    /// speed sacrifice.
    /// </summary>
    public struct ScriptValue
    {
        #region Fields
        /// <summary>
        /// The type that this value represents
        /// </summary>
        public   ScriptValueType type;
        /// <summary>
        /// The object containing the actual value
        /// </summary>
        public   object          value;
        internal object          typeInfo;
        #endregion

        #region Constructors
        //public ScriptValue()
        //{
        //    type  = ScriptValueType.Number;
        //    value = 0;
        //    typeinfo = null;
        //}
        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="aCopy">Value to make a copy from</param>
        public ScriptValue(ScriptValue aCopy)
        {
            type     = ScriptValueType.Number;
            value    = null;
            typeInfo = null;

            Copy(aCopy);
        }
        /// <summary>
        /// Constructor for an int value, automatically takes care of the details
        /// </summary>
        public ScriptValue(int         aValue)
        {
            type  = ScriptValueType.Number;
            value = aValue;
            typeInfo = null;
        }
        /// <summary>
        /// Constructor for a float value, automatically takes care of the details
        /// </summary>
        public ScriptValue(float       aValue)
        {
            type = ScriptValueType.Number;
            value = aValue;
            typeInfo = null;
        }
        /// <summary>
        /// Constructor for a string value, automatically takes care of the details
        /// </summary>
        public ScriptValue(string      aValue)
        {
            type     = ScriptValueType.String;
            value    = aValue;
            typeInfo = null;
        }
        /// <summary>
        /// Constructor for a Type value, automatically takes care of the details
        /// </summary>
        public ScriptValue(Type        aValue)
        {
            type     = ScriptValueType.Type;
            value    = aValue;
            typeInfo = null;
        }
        /// <summary>
        /// A constructor that automatically determines the type of the object, and
        /// takes care of details.
        /// </summary>
        public ScriptValue(object      aObj)
        {
            value    = aObj;
            typeInfo = null;
            if (value is float || value is int)
            {
                type = ScriptValueType.Number;
            }
            else if (value is string)
            {
                type = ScriptValueType.String;
            }
            else if (value is Type)
            {
                type = ScriptValueType.Type;
            }
            else
            {
                type = ScriptValueType.Object;
            }
        }

        /// <summary>
        /// This functions sets the value to the number zero
        /// </summary>
        public void Zero()
        {
            type     = ScriptValueType.Number;
            value    = 0;
            typeInfo = null;
        }
        #endregion

        #region Methods
        public void Copy(ScriptValue aValue)
        {
            value    = aValue.value;
            type     = aValue.type;
            typeInfo = aValue.typeInfo;
        }
        internal float GetAsFloat()
        {
            float ret = 0;
            if (type == ScriptValueType.Number)
            {
                if (value is float)
                    ret = (float)value;
                else
                    ret = (float)((int)value);
            }

            return ret;
        }
        #endregion

        #region Math Operator Methods
        internal void Add     (ref ScriptValue aRight)
        {
            switch (type)
            {
                case ScriptValueType.Number:
                    {
                        if (aRight.type == ScriptValueType.Number)
                        {
                            if (value is int)
                            {
                                if (aRight.value is int)
                                    value = (int)value + (int)aRight.value;
                                else if (aRight.value is float)
                                    value = (int)value + (float)aRight.value;
                            }
                            else
                            {
                                if (aRight.value is float)
                                    value = (float)value + (float)aRight.value;
                                else if (aRight.value is int)
                                    value = (float)value + (int)aRight.value;
                            }
                            
                        }
                        else
                            FerrScriptCompiler.Inst.ThrowException("Trying to add two values of unsupported types!");
                    } break;
                case ScriptValueType.String:
                    {
                        value = (string)value + aRight.value.ToString();
                    } break;
                default:
                    {
                        FerrScriptCompiler.Inst.ThrowException("Trying to add two values of unsupported types!");
                    } break;
            }
        }
        internal void Subtract(ref ScriptValue aRight)
        {
            switch (type)
            {
                case ScriptValueType.Number:
                    {
                        if (aRight.type == ScriptValueType.Number)
                        {
                            if (value is int)
                            {
                                if (aRight.value is int)
                                    value = (int)value - (int)aRight.value;
                                else if (aRight.value is float)
                                    value = (int)value - (float)aRight.value;
                            }
                            else
                            {
                                if (aRight.value is float)
                                    value = (float)value - (float)aRight.value;
                                else if (aRight.value is int)
                                    value = (float)value - (int)aRight.value;
                            }
                        }
                        else
                            FerrScriptCompiler.Inst.ThrowException("Trying to subtract two values of unsupported types!");
                    } break;
                default:
                    {
                        FerrScriptCompiler.Inst.ThrowException("Trying to subtract two values of unsupported types!");
                    } break;
            }
        }
        internal void Multiply(ref ScriptValue aRight)
        {
            switch (type)
            {
                case ScriptValueType.Number:
                    {
                        if (aRight.type == ScriptValueType.Number)
                        {
                            if (value is float)
                            {
                                if (aRight.value is float)
                                    value = (float)value * (float)aRight.value;
                                else if (aRight.value is int)
                                    value = (float)value * (int)aRight.value;
                            }
                            else
                            {
                                if (aRight.value is int)
                                    value = (int)value * (int)aRight.value;
                                else if (aRight.value is float)
                                    value = (int)value * (float)aRight.value;
                            }
                        }
                        else
                            FerrScriptCompiler.Inst.ThrowException("Trying to multiply two values of unsupported types!");
                    } break;
                default:
                    {
                        FerrScriptCompiler.Inst.ThrowException("Trying to multiply two values of unsupported types!");
                    } break;
            }
        }
        internal void Divide  (ref ScriptValue aRight)
        {
            switch (type)
            {
                case ScriptValueType.Number:
                    {
                        if (aRight.type == ScriptValueType.Number)
                        {
                            if (value is float)
                            {
                                if (aRight.value is float)
                                    value = (float)value / (float)aRight.value;
                                else if (aRight.value is int)
                                    value = (float)value / (int)  aRight.value;
                            }
                            else
                            {
                                if (aRight.value is int)
                                    value = (int)value / (int)aRight.value;
                                else if (aRight.value is float)
                                    value = (int)value / (float)aRight.value;
                            }
                        }
                        else
                            FerrScriptCompiler.Inst.ThrowException("Trying to divide two values of unsupported types!");
                    } break;
                default:
                    {
                        FerrScriptCompiler.Inst.ThrowException("Trying to divide two values of unsupported types!");
                    } break;
            }
        }
        #endregion

        #region Comparison Methods
        internal bool LargerThan  (ref ScriptValue aRight)
        {
            if (type == ScriptValueType.Number && aRight.type == ScriptValueType.Number)
            {
                if (value is int)
                {
                    if (aRight.value is int)
                    {
                        return (((int)value) > ((int)aRight.value));
                    }
                    else
                    {
                        return (((int)value) > ((float)aRight.value));
                    }
                }
                else
                {
                    if (aRight.value is int)
                    {
                        return (((float)value) > ((int)aRight.value));
                    }
                    else
                    {
                        return (((float)value) > ((float)aRight.value));
                    }
                }
            }
            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this + " & " + aRight);
            return false;
        }
        internal bool LessThan    (ref ScriptValue aRight)
        {
            if (type == ScriptValueType.Number && aRight.type == ScriptValueType.Number)
            {
                if (value is int)
                {
                    if (aRight.value is int)
                    {
                        return (((int)value) < ((int)aRight.value));
                    }
                    else
                    {
                        return (((int)value) < ((float)aRight.value));
                    }
                }
                else
                {
                    if (aRight.value is int)
                    {
                        return (((float)value) < ((int)aRight.value));
                    }
                    else
                    {
                        return (((float)value) < ((float)aRight.value));
                    }
                }
            }
            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
            return false;
        }
        internal bool LargerThanEq(ref ScriptValue aRight)
        {
            if (type == ScriptValueType.Number && aRight.type == ScriptValueType.Number)
            {
                if (value is int)
                {
                    if (aRight.value is int)
                    {
                        return (((int)value) >= ((int)aRight.value));
                    }
                    else
                    {
                        return (((int)value) >= ((float)aRight.value));
                    }
                }
                else
                {
                    if (aRight.value is int)
                    {
                        return (((float)value) >= ((int)aRight.value));
                    }
                    else
                    {
                        return (((float)value) >= ((float)aRight.value));
                    }
                }
            }
            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
            return false;
        }
        internal bool LessThanEq  (ref ScriptValue aRight)
        {
            if (type == ScriptValueType.Number && aRight.type == ScriptValueType.Number)
            {
                if (value is int)
                {
                    if (aRight.value is int)
                    {
                        return (((int)value) <= ((int)aRight.value));
                    }
                    else
                    {
                        return (((int)value) <= ((float)aRight.value));
                    }
                }
                else
                {
                    if (aRight.value is int)
                    {
                        return (((float)value) <= ((int)aRight.value));
                    }
                    else
                    {
                        return (((float)value) <= ((float)aRight.value));
                    }
                }
            }
            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
            return false;
        }
        internal bool IsEqual     (ref ScriptValue aRight)
        {
            switch (type)
            {
                case ScriptValueType.Number:
                    {
                        if (aRight.type == ScriptValueType.Number)
                        {
                            if (value is int)
                            {
                                if (aRight.value is int)
                                {
                                    return (((int)value) == ((int)aRight.value));
                                }
                                else
                                {
                                    return (((int)value) == ((float)aRight.value));
                                }
                            }
                            else
                            {
                                if (aRight.value is int)
                                {
                                    return (((float)value) == ((int)aRight.value));
                                }
                                else
                                {
                                    return (((float)value) == ((float)aRight.value));
                                }
                            }
                        }
                        else
                        {
                            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
                            return false;
                        }
                    } //break;
                case ScriptValueType.String:
                    {
                        if (aRight.type == ScriptValueType.String)
                        {
                            if (((string)value).Equals(aRight.value))
                                return true;
                            return false;
                        }
                        else
                        {
                            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
                            return false;
                        }
                    } //break;
                default:
                    {
                        FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
                        return false;
                    } //break;
            }
        }
        internal bool IsNotEqual  (ref ScriptValue aRight)
        {
            switch (type)
            {
                case ScriptValueType.Number:
                    {
                        if (aRight.type == ScriptValueType.Number)
                        {
                            if (value is int)
                            {
                                if (aRight.value is int)
                                {
                                    return (((int)value) != ((int)aRight.value));
                                }
                                else
                                {
                                    return (((int)value) != ((float)aRight.value));
                                }
                            }
                            else
                            {
                                if (aRight.value is int)
                                {
                                    return (((float)value) != ((int)aRight.value));
                                }
                                else
                                {
                                    return (((float)value) != ((float)aRight.value));
                                }
                            }
                        }
                        else
                        {
                            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
                            return false;
                        }
                    } //break;
                case ScriptValueType.String:
                    {
                        if (aRight.type == ScriptValueType.String)
                        {
                            if (((string)value).Equals(aRight.value))
                                return true;
                            return false;
                        }
                        else
                        {
                            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
                            return false;
                        }
                    } //break;
                default:
                    {
                        FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
                        return false;
                    } //break;
            }
        }
        internal bool IsAnd       (ref ScriptValue aRight)
        {
            if (type == ScriptValueType.Number && aRight.type == ScriptValueType.Number)
            {
                if (value is int)
                {
                    if (aRight.value is int)
                    {
                        return (((int)value > 0) && ((int)aRight.value > 0));
                    }
                    else
                    {
                        return (((int)value > 0) && ((float)aRight.value > 0));
                    }
                }
                else
                {
                    if (aRight.value is int)
                    {
                        return (((float)value > 0) && ((int)aRight.value > 0));
                    }
                    else
                    {
                        return (((float)value > 0) && ((float)aRight.value > 0));
                    }
                }
            }
            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
            return false;
        }
        internal bool IsOr        (ref ScriptValue aRight)
        {
            if (type == ScriptValueType.Number && aRight.type == ScriptValueType.Number)
            {
                if (value is int)
                {
                    if (aRight.value is int)
                    {
                        return (((int)value > 0) || ((int)aRight.value > 0));
                    }
                    else
                    {
                        return (((int)value > 0) || ((float)aRight.value > 0));
                    }
                }
                else
                {
                    if (aRight.value is int)
                    {
                        return (((float)value > 0) || ((int)aRight.value > 0));
                    }
                    else
                    {
                        return (((float)value > 0) || ((float)aRight.value > 0));
                    }
                }
            }
            FerrScriptCompiler.Inst.ThrowException("Comparing invalid types! " + this +" & " + aRight);
            return false;
        }
        #endregion

        #region Overrides
        public override string ToString()
        {
            return type.ToString() + ": " + (value != null ? value.ToString() : "null");
        }
        #endregion
    }
}